/**
 * Copyright 2010 Mark Wyszomierski
 */
package com.joelapenna.foursquare.http;

import com.joelapenna.foursquare.Foursquare;
import com.joelapenna.foursquare.parsers.json.Parser;
import com.joelapenna.foursquare.types.FoursquareType;
import com.joelapenna.foursquare.util.JSONUtils;

import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.SingleClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import android.util.Log;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;


/**
 * @date October 13, 2010
 * @author Mark Wyszomierski (markww@gmail.com)
 */
public class HttpImpl {
    protected static final Logger LOG = Logger.getLogger(AbstractHttpApi.class.getCanonicalName());
    protected static final boolean DEBUG = Foursquare.DEBUG;

    private static final String DEFAULT_CLIENT_VERSION = "com.joelapenna.foursquare";
    private static final String CLIENT_VERSION_HEADER = "User-Agent";
    private static final int TIMEOUT = 60;
    
    private static final int TYPE_GET  = 0;
    private static final int TYPE_POST = 1;

    private final String mClientVersion;
    private String mUsername;
    private String mPassword;
    //private String mToken;

    public HttpImpl(String clientVersion) {
        if (clientVersion != null) {
            mClientVersion = clientVersion;
        } else {
            mClientVersion = DEFAULT_CLIENT_VERSION;
        }
    }
    
    public boolean getHasCredentials() {
        return mUsername != null && mPassword != null &&
               mUsername.length() > 0 && mPassword.length() > 0;
    }
    
    public void setCredentials(String username, String password) {
        // TODO: this should be made thread-safe.
        mUsername = username;
        mPassword = password;
    }

    public FoursquareType httpGet(
            String url,
            Parser<? extends FoursquareType> parser,
            NameValuePair... nameValuePairs) throws Exception {
        return executeWithRetries(url, TYPE_GET, parser, nameValuePairs);
    }
    
    public FoursquareType httpPost(
            String url,
            Parser<? extends FoursquareType> parser,
            NameValuePair... nameValuePairs) throws Exception {
        return executeWithRetries(url, TYPE_POST, parser, nameValuePairs);
    }
    
    private FoursquareType executeWithRetries(String url, 
                                              int method,
                                              Parser<? extends FoursquareType> parser,
                                              NameValuePair... nameValuePairs) throws Exception{
        Log.v("^^^", "Ok, doing request...");
        
        Exception last = null;
        for (int i = 0; i < 3; i++) {
            try {
                String response = buildClient(createGetOrPost(method, url, nameValuePairs));
                return JSONUtils.consume(parser, response);
            } catch (Exception ex) {
                Log.v("^^^", "   Failed http connection attempt(" + i + ")");
                ex = last;
            }
        }
        
        throw last;
    }
    
    private HttpUriRequest createGetOrPost(int method, String url, NameValuePair ... nameValuePairs) 
        throws Exception {
        
        HttpUriRequest request = null;
        switch (method) {
            case TYPE_GET:
                String query = URLEncodedUtils.format(stripNulls(nameValuePairs), HTTP.UTF_8);
                request = new HttpGet(url + "?" + query);
                break;
            case TYPE_POST:
                HttpPost post = new HttpPost(url); 
                post.setEntity(new UrlEncodedFormEntity(stripNulls(nameValuePairs), HTTP.UTF_8));
                request = post;
                break;
            default:
                throw new IllegalStateException("Unknown http method supplied.");
        }
        
        // TODO: Inject OAuth2 token here.
        request.addHeader(CLIENT_VERSION_HEADER, mClientVersion);
        return request;
    }
    
    private static List<NameValuePair> stripNulls(NameValuePair... nameValuePairs) {
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        for (int i = 0; i < nameValuePairs.length; i++) {
            NameValuePair param = nameValuePairs[i];
            if (param.getValue() != null) {
                params.add(param);
            }
        }
        return params;
    }
    
    /** We'll dump this later, it's only used by user/update right now. */
    public HttpURLConnection createHttpURLConnectionPost(URL url, String boundary) 
        throws IOException {
        HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 
        conn.setDoInput(true);        
        conn.setDoOutput(true); 
        conn.setUseCaches(false); 
        conn.setConnectTimeout(TIMEOUT * 1000);
        conn.setRequestMethod("POST");
    
        conn.setRequestProperty(CLIENT_VERSION_HEADER, mClientVersion);
        conn.setRequestProperty("Connection", "Keep-Alive"); 
        conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
        
        return conn;
    }
    
    private String buildClient(HttpUriRequest request) throws Exception {

        SchemeRegistry supportedSchemes = new SchemeRegistry();
        SocketFactory sf = PlainSocketFactory.getSocketFactory();
        supportedSchemes.register(new Scheme("http", sf, 80));
        supportedSchemes.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));

        HttpParams params = new BasicHttpParams();
        HttpClientParams.setRedirecting(params, false);
        HttpConnectionParams.setConnectionTimeout(params, 10000);
        HttpConnectionParams.setSoTimeout(params, 10000);

        ClientConnectionManager conman = new SingleClientConnManager(params, supportedSchemes);
        DefaultHttpClient client = new DefaultHttpClient(conman, params);
        
        HttpRequestInterceptor preemptiveAuth = new HttpRequestInterceptor() {
            public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
                AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
                CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(
                        ClientContext.CREDS_PROVIDER);
                HttpHost targetHost = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
                
                if (authState.getAuthScheme() == null) {
                    AuthScope authScope = new AuthScope(targetHost.getHostName(), targetHost.getPort());
                    org.apache.http.auth.Credentials creds = credsProvider.getCredentials(authScope);
                    if (creds != null) {
                        authState.setAuthScheme(new BasicScheme());
                        authState.setCredentials(creds);
                    }
                }
            }    
        };
        client.addRequestInterceptor(preemptiveAuth, 0);
        client.getCredentialsProvider().setCredentials(
            new AuthScope("api.foursquare.com", 80),
            new UsernamePasswordCredentials(mUsername, mPassword));
        
        HttpResponse response = client.execute(request);
        if (response != null) {
            int statusCode = response.getStatusLine().getStatusCode();
            Log.v("^^^", "  It worked with code: " + statusCode);
            return EntityUtils.toString(response.getEntity());
        } else {
            Log.v("^^^", "  nope..");
            throw new Exception("HttpResponse was null!");
        }
    }
}
